#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "sm_trace.h"
#include "sm_kapi.h"
#include "itc.h"
#include "app_proxy.h"

#define DEC2BCD(dec) ((((dec)/10)<<4)+((dec)%10))
#define DT_APDU_BODY_SIZE		7

typedef struct
{
	int m_year;
	int m_month;
	int m_day;
	int m_hour;
	int m_minute;
	int m_second;
}dt_time_t;

#define DT_SESSION_COUNT 64
typedef struct
{
	unsigned char 	m_isUsed;
	short 			m_ssnb;
	unsigned char 	m_period;	//in ms
	unsigned long 	m_curtime; 	//in ms
}dt_session_table_t;

static dt_session_table_t 	s_dt_tab_session[DT_SESSION_COUNT];

//time when date time resource started, converted from UTC time
static dt_time_t s_dtStartTime={0, 0, 0, 0, 0, 0};
static unsigned short s_dtLocalOffset=0;

//time offset between the time when the machine starts
//and the time when the resource starts
static unsigned long s_dtStartAdjust;


static unsigned long local_mktime (unsigned int year, unsigned int mon,
								   unsigned int day, unsigned int hour,
									unsigned int min, unsigned int sec)
{
	//copyed from linux cornel
	if (0 >= (int) (mon -= 2))  // 1..12 -> 11,12,1..10
	{
		 mon += 12;              // Puts Feb last since it has leap day
		 year -= 1;
	}
	return ((((unsigned long)(year/4 - year/100 + year/400 + 367*mon/12 + day) +
			 year*365 - 719499)*24 + hour // now have hours
		   	)*60 + min // now have minutes
		 	)*60 + sec; // finally seconds
}


static int format_time_apdu(unsigned char* pbBuff)
{
	//according to the current time, format a apdu contain UTC_time and local_offset,
	struct tm 		tmGmt ;
	struct tm* 		pTmGmt;
	time_t 			t;
	int 			Y,M,D,L,MJD;
	int				nPos = 0;

	SM_TRACE_ENTRY(TRMOD_APP, "format_time_apdu() - entry\r\n");

	//get the current time -- seconds since 1970-01-01 00:00:00
	t=local_mktime(s_dtStartTime.m_year, s_dtStartTime.m_month, s_dtStartTime.m_day,
					s_dtStartTime.m_hour, s_dtStartTime.m_minute, s_dtStartTime.m_second);
	t = t+ SM_GetMsecCount()/1000 - s_dtStartAdjust;

	pTmGmt = gmtime(&t);
	if (pTmGmt == NULL)
	{
		SM_TRACE_ENTRY(TRMOD_APP, "format_time_apdu() - gmtime failed\r\n");
		return -1;
	}

	tmGmt = *pTmGmt;
	Y = tmGmt.tm_year ;
	M = tmGmt.tm_mon + 1 ;
	D = tmGmt.tm_mday ;
	L = (M==1 || M==2) ? 1 : 0 ;
	MJD = 14956 + D +(int)((Y-L)*365.25) + (int)((M+1+L*12)*30.6001) ;

	nPos = 0;
	pbBuff[nPos++] = MJD / 256;
	pbBuff[nPos++] = MJD & 0xff;
	pbBuff[nPos++] = DEC2BCD(tmGmt.tm_hour);
	pbBuff[nPos++] = DEC2BCD(tmGmt.tm_min);
	pbBuff[nPos++] = DEC2BCD(tmGmt.tm_sec);
	pbBuff[nPos++] = s_dtLocalOffset / 256;
	pbBuff[nPos++] = s_dtLocalOffset & 0xff;
	SM_ASSERT(nPos == DT_APDU_BODY_SIZE);

	SM_TRACE_RETURN(TRMOD_APP, "format_time_apdu() - return 0\r\n");
	return OK;
}


static void dt_end(void)
{
	SM_TRACE_ENTRY(TRMOD_APP, "dt_end() - entry\r\n");

	ITC_End();

	SM_TRACE_RETURN(TRMOD_APP, "dt_end() - return\r\n");
	return;
}


static int datetime_task(void * param)
{
	int 			i;
	int				rc = 0;
	unsigned char 	buffer[DT_APDU_BODY_SIZE];
	xmsg_t			xmsg;

	SM_TRACE_CRITICAL(TRMOD_APP, "datetime_task() - entry\r\n");

	if (!ITC_Init(ITC_DT))
	{
		SM_TRACE_ENTRY(TRMOD_APP, "datetime_task() - itc init failed\r\n");
		rc = -1;
	}

	while (rc == 0)
	{
		//SM_TRACE_INFO(TRMOD_APP, "datetime_task() - wait for event infinite...\r\n");

		ITC_RecvXmsg(&xmsg, 100);

		if (xmsg.m_type == XMG_EXIT)
		{
			SM_TRACE_CRITICAL(TRMOD_APP, "datetime_task() - receive XMG_EXIT\r\n");
			break;
		}

//		ITC_Sleep(100); //sleep 100ms.
		for (i = 0; i < DT_SESSION_COUNT; i++)
		{
			if (!s_dt_tab_session[i].m_isUsed || !s_dt_tab_session[i].m_period)
				continue;

			s_dt_tab_session[i].m_curtime+=100;
			if(s_dt_tab_session[i].m_curtime>=(unsigned long)s_dt_tab_session[i].m_period)
			{
				s_dt_tab_session[i].m_curtime=0;
				if(format_time_apdu(buffer) == OK)
				{
					SM_TRACE_INFO(TRMOD_APP,"datetime_task() - send date_time, ssnb: %d\r\n",
								 s_dt_tab_session[i].m_ssnb);
					send_app_apdu_to_proxy(s_dt_tab_session[i].m_ssnb, CI_APDU_TAG_DATE_TIME, buffer, DT_APDU_BODY_SIZE); //date_time
				}
			}
		}
	}

	dt_end();

	SM_TRACE_CRITICAL(TRMOD_APP, "datetime_task() - return rc=%d\r\n", rc);
	return rc;
}


int dt_init()
{
	int i;
	SM_TRACE_ENTRY(TRMOD_APP, "dt_init() - entry\r\n");

	for(i=0;i<DT_SESSION_COUNT;i++)
		s_dt_tab_session[i].m_isUsed=0;

	SM_CreateTask("dt", datetime_task, NULL, NULL, 40000, 40, TRUE, ITC_DT);

	SM_TRACE_RETURN(TRMOD_APP, "dt_init() - return 0\r\n");
	return 0;
}


int dt_on_message(xmsg_t* pXmsg)
{
	int 			i;
	unsigned char	tcid;
	unsigned short	ssnb;
	xmsg_t			xmsg;

	SM_ASSERT(pXmsg);
	SM_TRACE_ENTRY(TRMOD_APP, "dt_on_message() - entry,pXmsg=0x%08X\r\n", pXmsg);

	SM_TRACE_INFO(TRMOD_APP, "dt_on_message() - %s(0x%02X) received\r\n",
				XMSG_NAME(pXmsg->m_type),pXmsg->m_type);

	switch(pXmsg->m_type)
	{
	case APP_OPEN_SS_REQ:
		ssnb = pXmsg->m_bd.m_app_open_ss_req.m_ssnb;
		tcid = pXmsg->m_bd.m_app_open_ss_req.m_tcid;
		SM_TRACE_INFO(TRMOD_APP, "dt_on_message() - APP_OPEN_SS_REQ received, ssnb=%d\r\n", ssnb);
		for(i=0;i<DT_SESSION_COUNT;i++)
		{
			if(!s_dt_tab_session[i].m_isUsed)
				break;
		}
		memset(&xmsg, 0x00, sizeof(xmsg));
		xmsg.m_sndrcver = (RM << 4) | RM;
		xmsg.m_type  = APP_OPEN_SS_RSP;
		xmsg.m_bd.m_app_open_ss_rsp.m_tcid = tcid;
		xmsg.m_bd.m_app_open_ss_rsp.m_ssnb = ssnb;
		xmsg.m_bd.m_app_open_ss_rsp.m_ressId = pXmsg->m_bd.m_app_open_ss_req.m_ressId;
		if(i<DT_SESSION_COUNT)
		{
			xmsg.m_bd.m_app_open_ss_rsp.m_status = SS_OK;
			s_dt_tab_session[i].m_isUsed=1;
			s_dt_tab_session[i].m_ssnb=ssnb;
			s_dt_tab_session[i].m_period=0;
		}
		else
		{
			xmsg.m_bd.m_app_open_ss_rsp.m_status = SS_NOTAVAILABLE;
		}
		SM_TRACE_INFO(TRMOD_APP, "dt_on_message() - will send APP_OPEN_SS_RSP to PROXY\r\n");
		ITC_SendXmsg(&xmsg);
		break;

	case APP_SS_OPENED:
		ssnb=pXmsg->m_bd.m_app_ss_opened.m_ssnb;
		SM_TRACE_INFO(TRMOD_APP, "dt_on_message() - APP_SS_OPENED received,ssnb=%d\r\n",ssnb);
		break;
	case APP_SS_BROKEN:
		ssnb=pXmsg->m_bd.m_app_ss_broken.m_ssnb;
		for(i=0;i<DT_SESSION_COUNT;i++)
		{
			if(s_dt_tab_session[i].m_isUsed && (ssnb==s_dt_tab_session[i].m_ssnb))
			{
				break;
			}
		}
		if(DT_SESSION_COUNT>i)
			s_dt_tab_session[i].m_isUsed=0;
		break;

	case APP_APDU:
	{
		unsigned long 	apduTag;
		unsigned long 	bodyLen;
		long 			totalLen;
		unsigned char 	buffer[DT_APDU_BODY_SIZE];
		unsigned char* 	pbBody = NULL;
		unsigned char* 	pbApdu = NULL;

		totalLen=pXmsg->m_bd.m_app_apdu.m_apduLen;
		ssnb=pXmsg->m_bd.m_app_apdu.m_ssnb;
		pbApdu = pXmsg->m_bd.m_app_apdu.m_pbApdu;
		for(;totalLen>0;)
		{
			pbBody=decode_apdu(pbApdu, &apduTag, &bodyLen);
			totalLen = totalLen-(((unsigned long)pbBody)-((unsigned long)pbApdu)) - bodyLen;
			pbApdu = pbBody + bodyLen;//point to the next apdu
			if(totalLen<0)
			{
				SM_TRACE_INFO(TRMOD_APP,"dt_on_message() - length error in apdu\r\n");
				break;
			}
			switch(apduTag)
			{
			case CI_APDU_TAG_DATE_TIME_ENQ://date time enquiry
				for(i=0;i<DT_SESSION_COUNT;i++)
				{
					if((s_dt_tab_session[i].m_isUsed)&&(ssnb==s_dt_tab_session[i].m_ssnb))
						break;
				}
				if (i < DT_SESSION_COUNT)
				{
					s_dt_tab_session[i].m_curtime = 0;
					s_dt_tab_session[i].m_period = pbBody[0] * 1000; //second to ms
					if(format_time_apdu(buffer) == OK)
					{
						SM_TRACE_INFO(TRMOD_APP,"dt_on_message() - send date_time, ssnb: %d\r\n",
									ssnb);
						send_app_apdu_to_proxy(ssnb, CI_APDU_TAG_DATE_TIME, buffer, DT_APDU_BODY_SIZE); //date_time
					}
				}
				break;
			default:
				SM_TRACE_ERROR(TRMOD_APP,"dt_on_message() - unkown apdu apduTag=0x%06X\r\n", apduTag);
				break;
			}
		}
		break;
	}

	default:
		SM_TRACE_ERROR(TRMOD_APP,"dt_on_message() - unkown mess type=0x%02X\r\n", pXmsg->m_type);
		break;
	}

	SM_TRACE_RETURN(TRMOD_APP, "dt_on_message() - return\r\n");
	return 0;
}


int dt_set_utc_time(unsigned char* utc_time)
{
	//interface to other parts of host software
	int mjd, y1, m1, k;

	SM_TRACE_ENTRY(TRMOD_APP, "dt_set_utc_time() - entry\r\n");

	mjd=utc_time[0]*256+utc_time[1];
	y1=(int)((mjd-15078.2)/365.25);
	m1=(int)((mjd-14956.1-(int)(y1*365.25))/30.6001);

	s_dtStartTime.m_day=mjd-14956-(int)(y1*365.25)-(int)(m1*30.6001);
	if((m1==14)||(m1==15))
	{
		k=1;
	}
	else
	{
		k=0;
	}
	s_dtStartTime.m_year=y1+k;
	s_dtStartTime.m_month=m1-1-k*12;
	s_dtStartTime.m_hour=(utc_time[2]&0xf0)/16*10+(utc_time[2]&0xf);
	s_dtStartTime.m_minute=(utc_time[3]&0xf0)/16*10+(utc_time[3]&0xf);
	s_dtStartTime.m_second=(utc_time[4]&0xf0)/16*10+(utc_time[4]&0xf);

	s_dtStartAdjust=SM_GetMsecCount()/1000; //ms to seconds.

	SM_TRACE_RETURN(TRMOD_APP, "dt_set_utc_time() - return\r\n");
	return 0;
}

int dt_set_local_time_offset(int local_offset)
{
	//interface to other parts of host software
	SM_TRACE_ENTRY(TRMOD_APP, "dt_set_local_time_offset() - entry,local_offset=%d\r\n", local_offset);

	s_dtLocalOffset=local_offset;

	SM_TRACE_RETURN(TRMOD_APP, "dt_set_local_time_offset() - return\r\n");
	return 0;
}
